using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using RideMe.Core.SortableCollection;

namespace RideMe.Core.Parser
{
    /// <summary>
    /// Parser
    /// <remarks>
    /// This needs a lot of serious testing. I tried it 
    /// with my code, and with Typo, but that's about it.
    /// Also, RegularExpressions are so much better 
    /// suited for this kind of text manipulation. So...
    /// </remarks>
    /// </summary>
    public static class RubyParser
    {
        public static event EventHandler ParseComplete;

        private static void OnParseComplete()
        {
            if (ParseComplete != null)
            {
                ParseComplete(null, EventArgs.Empty);
            }
        }

        private const string ClassKeyword = "class";
        private const string ModuleKeyword = "module";
        private const string MethodKeyword = "def";
        private const string Comment = "#";
        private const string AssignmentOperator = "=";
        private const string RaiseKeyword = "raise";
        private const string EndKeyword = "end";
        private const string EndDotKeyword = "end.";
        private const string EndParenKeyword = "end)";
        private const string DoKeyword = "do";
        private const string RescueKeword = "rescue";
        private const string CaseKeyword = "case";
        private const char Tab = '\t';

        private static string[] _conditionalTokens = new string[] {
            "if", "unless", DoKeyword, CaseKeyword, "begin", "for", "while"};

        private static string _buffer;

        public static string Buffer
        {
            get { return _buffer; }
            set { _buffer = value; }
        }

        private static SortableCollection<TopLevelEntity> _topLevelEntities;

        public static SortableCollection<TopLevelEntity> TopLevelEntities
        {
            get { return _topLevelEntities; }
            set { _topLevelEntities = value; }
        }

        public static void Parse(string buffer)
        {
            _buffer = buffer;
            Parse();
        }

        private static IRubyEntity _lastEntity;

        public static void Parse()
        {
			InitTopLevelEntities();

            string[] digestedBuffer = _buffer.Split((FileSystemService.GetLineEndingCharacter(LineEndingStyle.Unix).ToCharArray()));

            // Used to track how many ends we need to skip.
            int conditionalCount = 0;

            TopLevelEntity lastTopLevelEntity = null;

            for (int i = 0; i < digestedBuffer.Length; i++)
            {
                string line = digestedBuffer[i].Trim(new char[] { Tab, '\r' });

                if (IsComment(line)) continue;

                if (IsTopLevelEntity(line))
                {
					lastTopLevelEntity = HandleTopLevelEntity(lastTopLevelEntity, i, line);
                }
                else if (IsMethod(line) && lastTopLevelEntity != null)
                {
					HandleMethod(lastTopLevelEntity, i, line);
                }
                else if (IsConditionalStatementWithEnd(line))
                {
                    // We need to skip over all ends that match up
                    // to a conditional.
                    conditionalCount++;
                }
                else if (IsEndStatement(line))
                {
                    if (conditionalCount > 0)
                    {
                        conditionalCount--;
                        continue;
                    }

                    MatchEndWithEntity(CreateEndStatement(line, i));
                }
            }

            OnParseComplete();
        }

		private static void HandleMethod(TopLevelEntity lastTopLevelEntity, int i, string line)
		{
			Method newMethod = CreateMethod(line, i);
			TopLevelEntity parentEntity = _lastEntity as TopLevelEntity;
			if (parentEntity == null || parentEntity.End != null)
				parentEntity = lastTopLevelEntity;

			parentEntity.Methods.Add(newMethod);
			newMethod.Parent = parentEntity;
		}

		private static TopLevelEntity HandleTopLevelEntity(TopLevelEntity lastTopLevelEntity, int i, string line)
		{
			TopLevelEntity topLevelEntity = CreateTopLevelEntity(line, i);

			if (lastTopLevelEntity == null)
			{
				lastTopLevelEntity = topLevelEntity;
				_topLevelEntities.Add(topLevelEntity);
				_lastEntity = topLevelEntity;
			}

			// If it's not closed, then this is a nested class/module.
			if ((lastTopLevelEntity.End != null))
			{
				_topLevelEntities.Add(topLevelEntity);
				lastTopLevelEntity = topLevelEntity;
				_lastEntity = topLevelEntity;
			}
			else
			{
				if (lastTopLevelEntity != topLevelEntity)
				{
					lastTopLevelEntity.SubEntities.Add(topLevelEntity);
					_lastEntity = topLevelEntity;
				}
			}
			return lastTopLevelEntity;
		}

		private static void InitTopLevelEntities()
		{
			if (_topLevelEntities == null)
			{
				_topLevelEntities = new SortableCollection<TopLevelEntity>();
			}
			else
			{
				_topLevelEntities.Clear();
			}
		}

        private static bool IsTopLevelEntity(string line)
        {
            if (IsEndOnThisLine(line)) return false;
            return (LineStartsWithToken(line, ClassKeyword)) || (LineStartsWithToken(line, ModuleKeyword));
        }

        private static bool IsMethod(string line)
        {
            // Empty method with end on the same line 
            if (IsEndOnThisLine(line)) return false;
            return LineStartsWithToken(line, MethodKeyword);
        }

        private static bool IsConditionalStatementWithEnd(string line)
        {
            int position;

            if (ContainsToken(line, out position, _conditionalTokens))
            {
                if (IsEndOnThisLine(line)) return false;

                string[] words = GetWords(line);

                // The do keyword is not at the beginng of the line.
                if ((words[position] != DoKeyword))
                {
                    if (IsInlineConditional(line, position)) return false;
                }

                return true;
            }

            return false;
        }

        public static bool IsEndStatement(string line)
        {
            if (line == null) return false;

            // Sometimes you'll see code like end.sort_by.
            // But startwith token looks for the exact 
            // token, so check for that as well.
            int endDotPosition = line.IndexOf(EndDotKeyword);

            int endParenPosition = line.IndexOf(EndParenKeyword);

            if (endDotPosition != -1)
            {
                return LineStartsWithPartialToken(line, EndDotKeyword);
            }
            else if (endParenPosition != -1)
            {
                return LineStartsWithPartialToken(line, EndParenKeyword);
            }

            return LineStartsWithToken(line, EndKeyword);
        }

        private static TopLevelEntity CreateTopLevelEntity(string line, int linePosition)
        {
            TopLevelEntity newTopLevelEntity = new TopLevelEntity();
            newTopLevelEntity.Name = GetEntityName(line, ClassKeyword, ModuleKeyword);
            newTopLevelEntity.Column = line.Length;
            newTopLevelEntity.Line = linePosition;
            newTopLevelEntity.LineLength = line.Length;

            return newTopLevelEntity;
        }

        private static Method CreateMethod(string line, int linePosition)
        {
            Method newMethod = new Method();
            newMethod.Name = GetEntityName(line, MethodKeyword);
            newMethod.Column = line.Length;
            newMethod.Line = linePosition;
            newMethod.LineLength = line.Length;

            return newMethod;
        }

        private static EndStatement CreateEndStatement(string line, int linePosition)
        {
            EndStatement newEndStatement = new EndStatement();
            newEndStatement.Name = EndKeyword;
            newEndStatement.Line = linePosition;
            newEndStatement.LineLength = line.Length;

            return newEndStatement;
        }

        private static bool LineStartsWithToken(string line, string token)
        {
            string[] words = GetWords(line);

            foreach (string word in words)
            {
                string trimmedWord = word.TrimStart(Tab);

                if (trimmedWord.Trim().Length != 0)
                {
                    return trimmedWord == token;
                }
            }

            return false;
        }

        private static bool LineStartsWithPartialToken(string line, string token)
        {
            string[] words = GetWords(line);


            foreach (string word in words)
            {
                string trimmedWord = word.TrimStart(Tab);

                int partialPosition = word.IndexOf(token);

                if ((trimmedWord.Trim().Length != 0) & (partialPosition != -1))
                {
                    return trimmedWord.Substring(partialPosition, token.Length) == token;
                }
            }

            return false;
        }

        private static int GetTokenPosition(string line, string token)
        {

            string[] words = GetWords(line);

            for (int i = 0; i < words.Length; i++)
            {
                string word = words[i].TrimStart(Tab);

                if (word.Trim().Length == 0) continue;

                if (word == token)
                {
                    return i;
                }
            }

            return -1;
        }

        private static string GetEntityName(string line, params string[] tokens)
        {
            foreach (string tokenName in tokens)
            {
                int tokenPosition = GetTokenPosition(line, tokenName);

                string[] words = GetWords(line);

                if (tokenPosition != -1)
                {
                    for (int i = tokenPosition + 1; i < words.Length; i++)
                    {
                        if (words[i].Trim().Length != 0)
                        {
                            return words[i];
                        }
                    }
                }
            }
            return string.Empty;
        }

        private static bool IsComment(string line)
        {
            return line.Trim(new char[]{' ', Tab}).StartsWith(Comment);
        }

        private static bool ContainsToken(string line, out int position, params string[] tokens)
        {
            position = -1;

            string[] words = GetWords(line);

            foreach (string token in tokens)
            {
                for (int i = 0; i < words.Length; i++)
                {
                    string word = words[i].TrimStart(Tab);

                    if (word.Trim().Length == 0) continue;

                    if (word == token)
                    {
                        if (IsInStringLiteral(line, i)) return false;

                        position = i;
                        return true;
                    }
                }
            }

            return false;
        }

        private static bool IsInlineConditional(string line, int wordPosition)
        {
            if (wordPosition == 0)
            {
                return false;
            }

            int i = wordPosition - 1;

            string[] words = GetWords(line);

            // Step backwards through the line starting at the wordPosition (conditional in question)
            // checking to see if there's a word before this one other than white space. If so
            // then we can reasonably assume this is an inline statement. Mmmkay?
            while (i >= 0)
            {
                if ((words[i].Trim().Length != 0))
                {
                    return (words[i].Trim().Length != 0) & (words[i] != CaseKeyword) & (words[i] != AssignmentOperator) & (words[i] != RaiseKeyword);
                }
                i--;
            }

            return false;
        }

        private static bool IsEndOnThisLine(string line)
        {
            string[] words = GetWords(line);

            foreach (string word in words)
            {
                if (word == EndKeyword)
                {
                    return true;
                }
            }

            return false;
        }

        private static bool IsInStringLiteral(string line, int wordPosition)
        {

            if (wordPosition == 0)
            {
                return false;
            }

            int i = wordPosition - 1;
            int numberOfStrings = 0;

            string[] words = GetWords(line);

            // Step backwards through the line starting at the wordPosition (conditional in question)
            // checking to see if there's a word before this one other than white space. If so
            // then we can reasonably assume this is an inline statement. Mmmkay?
            while (i != 0)
            {
                if ((words[i].Trim().Length != 0) & (words[i] == "\"") || (words[i] == "\'"))
                {
                    numberOfStrings++;
                }
                i--;
            }

            // If there are an odd number of " to the left of the token 
            // assume the string hasn't been closed. 

            return numberOfStrings % 2 != 0;
        }

        private static void MatchEndWithEntity(EndStatement end)
        {
            foreach (TopLevelEntity topLevelEntity in _topLevelEntities)
            {
                foreach (Method method in topLevelEntity.Methods)
                {
                    if (method.End == null)
                    {
                        method.End = end;
                        return;
                    }
                }

                if (topLevelEntity.SubEntities.Count == 0)
                {
                    if (topLevelEntity.End == null)
                    {
                        topLevelEntity.End = end;
                        return;
                    }
                }

                int i = topLevelEntity.SubEntities.Count;

                if (i != 0)
                {
                    foreach (TopLevelEntity subEntity in topLevelEntity.SubEntities)
                    {
                        foreach (Method method in subEntity.Methods)
                        {
                            if (method.End == null)
                            {
                                method.End = end;
                                return;
                            }
                        }
                    }
                }
    
                while (i != 0)
                {
                    if (topLevelEntity.SubEntities[i - 1].End == null)
                    {
                        topLevelEntity.SubEntities[i - 1].End = end;
                        return;
                    }
                    i--;
                }

                if (topLevelEntity.End == null)
                {
                    topLevelEntity.End = end;
                    return;
                }
            }
        }

        private static string[] GetWords(string line)
        {
            return line.Split(new char[] { ' ', '(' });
        }

        public static Method FindMethod(string name)
        {
            foreach (Method method in _topLevelEntities[0].Methods)
            {
                if (method.Name == name)
                {
                    return method;
                }
            }

            return SearchSubEntities(name, _topLevelEntities[0].SubEntities);
        }

        private static Method SearchSubEntities(string name, SortableCollection<TopLevelEntity> entities)
        {
            foreach (TopLevelEntity entity in entities)
            {
                foreach (Method method in entity.Methods)
                {
                    if (method.Name == name)
                    {
                        return method;
                    }
                }

                SearchSubEntities(name, entity.SubEntities);
            }

            return null;
        }
	


    }
}